// 版权所有2009 Go作者。版权所有。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

// 垃圾收集器：清扫

// 清扫器由两种不同的算法组成：
// 
// /*对象回收器查找并释放跨中未标记的插槽。如果没有标记任何对象，它可以释放整个跨度，但它的目标不是它。这可以由
// mcentral.cacheSpan同步驱动，也可以由查看所有mcentral列表的
// sweepone异步驱动。
// 
// *跨度回收器查找不包含标记对象的跨度
// 并释放整个跨度。这是一个单独的算法，因为
// 释放整个跨度是对象回收器最困难的任务，
// 但在分配新跨度时非常关键。
// 的入口点是mheap_2;.recall，它由
// 的顺序扫描驱动，页面在堆区域中标记位图。
// 
// 这两种算法最终都调用mspan.sweep，它扫描单个
// 堆跨度。

package runtime

import (
	"runtime/internal/atomic"
	"unsafe"
)

var sweep sweepdata

// 背景扫描状态。
type sweepdata struct {
	lock    mutex
	g       *g
	parked  bool
	started bool

	nbgsweep    uint32
	npausesweep uint32

	// centralIndex是当前未扫描的span类。
	// 表示mcentral span的索引
	// 集合。通过其加载和
	// 更新方法访问和更新。没有锁的没有锁的。
	// 
	// 在标记终止时重置。
	// 由mheap.nextSpanForSweep使用。
	centralIndex sweepClass
}

// sweepClass是一个span类，一个位表示我们当前使用的是
// 扫描部分跨度还是完整跨度。
type sweepClass uint32

const (
	numSweepClasses            = numSpanClasses * 2
	sweepClassDone  sweepClass = sweepClass(^uint32(0))
)

func (s *sweepClass) load() sweepClass {
	return sweepClass(atomic.Load((*uint32)(s)))
}

func (s *sweepClass) update(sNew sweepClass) {
	// 仅当*s的当前值小于sNew时更新，
	// 因为*s单调增加。这不是唯一一个我们有原子单调递增计数器的地方。拥有一个“atomic max”会很好，它刚刚在大多数体系结构上实现了上述功能。但是，有些架构
	sOld := s.load()
	for sOld < sNew && !atomic.Cas((*uint32)(s), uint32(sOld), uint32(sNew)) {
		sOld = s.load()
	}
	// 比如RISC-V本身就支持atomic max。
}

func (s *sweepClass) clear() {
	atomic.Store((*uint32)(s), 0)
}

// split返回基础的span类以及
// 我们是否对该类的完整或部分
// 未删除列表感兴趣，表示为布尔
// /（true表示“完整”）。
func (s sweepClass) split() (spc spanClass, full bool) {
	return spanClass(s >> 1), s&1 == 0
}

// nextSpanForSweep从
// 中央扫描缓冲区查找并弹出下一个扫描范围。它将范围的所有权返回给调用者。
// 如果不存在这样的跨度，则返回nil。
func (h *mheap) nextSpanForSweep() *mspan {
	sg := h.sweepgen
	for sc := sweep.centralIndex.load(); sc < numSweepClasses; sc++ {
		spc, full := sc.split()
		c := &h.central[spc].mcentral
		var s *mspan
		if full {
			s = c.fullUnswept(sg).pop()
		} else {
			s = c.partialUnswept(sg).pop()
		}
		if s != nil {
			// 写下我们发现了一些东西，以便未来的清洁工可以从这里开始。
			sweep.centralIndex.update(sc)
			return s
		}
	}
	// 写下我们什么也没找到。
	sweep.centralIndex.update(sweepClassDone)
	return nil
}

// finishsweep_m确保扫描所有跨度。世界必须停止。这确保
// 进程中没有扫描。
// 
// go:nowritebarrier 
func finishsweep_m() {
	assertWorldStopped()

	// 标记开始前必须完成扫掠，因此
	// 扫掠所有未扫掠的跨距。如果这是一个并发GC，那么应该没有任何跨度需要扫描，因此应该立即完成。如果在并发扫描
	// 完成之前强制执行GC，则可能存在要扫描的跨距。
	for sweepone() != ^uintptr(0) {
		sweep.npausesweep++
	}

	// 重置所有未扫描的缓冲区，该缓冲区应为空。
	// 在扫描终止中执行此操作，而不是标记终止
	// 以便我们可以捕获未扫描的跨距，并尽快将块回收为
	// 。清扫工作已经完成，如果清道夫还没有醒来，请唤醒它。在这个
	sg := mheap_.sweepgen
	for i := range mheap_.central {
		c := &mheap_.central[i].mcentral
		c.partialUnswept(sg).reset()
		c.fullUnswept(sg).reset()
	}

	// /点上，它肯定有工作要做。
	wakeScavenger()

	nextMarkBitArenaEpoch()
}

func bgsweep() {
	sweep.g = getg()

	lockInit(&sweep.lock, lockRankSweep)
	lock(&sweep.lock)
	sweep.parked = true
	gcenable_setup <- 1
	goparkunlock(&sweep.lock, waitReasonGCSweepWait, traceEvGoBlock, 1)

	for {
		for sweepone() != ^uintptr(0) {
			sweep.nbgsweep++
			Gosched()
		}
		for freeSomeWbufs(true) {
			Gosched()
		}
		lock(&sweep.lock)
		if !isSweepDone() {
			// 如果GC在
			// gosweepone返回
			// 上方^0和正在获取的锁之间运行，则可能发生这种情况。
			unlock(&sweep.lock)
			continue
		}
		sweep.parked = true
		goparkunlock(&sweep.lock, waitReasonGCSweepWait, traceEvGoBlock, 1)
	}
}

// sweepLocker获得跨度和块的扫描所有权扫描
// 完成。
type sweepLocker struct {
	// sweepGen是堆的扫描生成。
	sweepGen uint32
	// 阻塞表示此跟踪器正在阻塞扫描
	// 完成，通常是由于获取了至少一个跨度的扫描
	// 所有权。
	blocking bool
}

// sweepLocked表示范围的扫描所有权。
type sweepLocked struct {
	*mspan
}

func newSweepLocker() sweepLocker {
	return sweepLocker{
		sweepGen: mheap_.sweepgen,
	}
}

// 尝试获取span s的扫描所有权。如果它
// 成功获得所有权，它将阻止扫描完成。
func (l *sweepLocker) tryAcquire(s *mspan) (sweepLocked, bool) {
	// 在尝试CAS之前进行检查。
	if atomic.Load(&s.sweepgen) != l.sweepGen-2 {
		return sweepLocked{}, false
	}
	// 在可能获得
	// 所有权之前加入清扫者。
	l.blockCompletion()
	// 试图获得s。
	if !atomic.Cas(&s.sweepgen, l.sweepGen-2, l.sweepGen-1) {
		return sweepLocked{}, false
	}
	return sweepLocked{s}, true
}

// 块完成块扫描完成，无需获取任何
// 特定跨距。
func (l *sweepLocker) blockCompletion() {
	if !l.blocking {
		atomic.Xadd(&mheap_.sweepers, +1)
		l.blocking = true
	}
}

func (l *sweepLocker) dispose() {
	if !l.blocking {
		return
	}
	// 减少活动清扫器的数量，如果这是
	// 最后一个，则将清扫标记为完成。
	l.blocking = false
	if atomic.Xadd(&mheap_.sweepers, -1) == 0 && atomic.Load(&mheap_.sweepDrained) != 0 {
		l.sweepIsDone()
	}
}

func (l *sweepLocker) sweepIsDone() {
	if debug.gcpacertrace > 0 {
		print("pacer: sweep done at heap size ", gcController.heapLive>>20, "MB; allocated ", (gcController.heapLive-mheap_.sweepHeapLiveBasis)>>20, "MB during sweep; swept ", mheap_.pagesSwept, " pages at ", mheap_.sweepPagesPerByte, " pages/byte\n")
	}
}

// 扫描一个扫描未扫描的堆范围并返回返回到堆的页数
// 如果没有要扫描的内容，则返回^uintpttr（0）。ABCFDG＊＊＊＊＊＊＊＊/增量锁，以确保GOODUTIN不被抢占ABCFDG 
func sweepone() uintptr {
	_g_ := getg()

	_g_.m.locks++
	if atomic.Load(&mheap_.sweepDrained) != 0 {
		_g_.m.locks--
		return ^uintptr(0)
	}
	// 将清扫柜提升到呼叫者中。
	sl := newSweepLocker()

	// 找到要扫描的跨度。
	npages := ^uintptr(0)
	var noMoreWork bool
	for {
		s := mheap_.nextSpanForSweep()
		if s == nil {
			noMoreWork = atomic.Cas(&mheap_.sweepDrained, 0, 1)
			break
		}
		if state := s.state.get(); state != mSpanInUse {
			// 如果直接扫描已经扫过此跨度，则可能发生这种情况，但在这种情况下，扫描
			// 生成应始终是最新的。
			if !(s.sweepgen == sl.sweepGen || s.sweepgen == sl.sweepGen+3) {
				print("runtime: bad span s.state=", state, " s.sweepgen=", s.sweepgen, " sweepgen=", sl.sweepGen, "\n")
				throw("non in-use span in unswept list")
			}
			continue
		}
		if s, ok := sl.tryAcquire(s); ok {
			// 扫一扫我们找到的跨度。
			npages = s.npages
			if s.sweep(false) {
				// 整个跨度被释放。将其计入
				// 页面回收者信用，因为这些页面现在可以用于范围分配。
				atomic.Xadduintptr(&mheap_.reclaimCredit, npages)
			} else {
				// Span仍在使用中，因此此操作未将任何
				// 页返回到堆中，Span需要将
				// 移动到已使用列表中。
				npages = 0
			}
			break
		}
	}

	sl.dispose()

	if noMoreWork {
		// 扫描列表为空。可能仍在运行
		// 并发扫描，但我们至少非常
		// 即将完成扫描。

		// 将清道夫向前移动（表示有新工作要做）并唤醒清道夫。
		// 
		// 清洗器由最后一个清洗器发出信号，因为一旦完成
		// 清洗器，我们肯定会为
		// 清洗器做有用的工作，因为清洗器在每个GC循环中只在
		// 堆上运行一次。在扫描
		// 终止期间不会执行此更新，因为在某些情况下，在扫描完成和扫描终止之间可能会有很长的延迟
		// 例如，没有足够的
		// 分配来触发GC），这将很好地用清除工作填充
		// 。
		systemstack(func() {
			lock(&mheap_.lock)
			mheap_.pages.scavengeStartGen()
			unlock(&mheap_.lock)
		})
		// 因为我们可能在分配路径中进行扫描，所以我们不可能通过WakeCleaver直接唤醒清道夫，因为
		// 它可以分配。请sysmon替我们做这件事。
		readyForScavenger()
	}

	_g_.m.locks--
	return npages
}

// isSweepDone报告是否扫描了所有跨度。
// 
// 请注意，随着清扫器的运行，此条件可能会在任何
// 时间从false转换为true。如果运行
// GC，则可能会从true转换为false；为了防止调用方必须是不可抢占的或必须
// 以某种方式阻止GC进程。
func isSweepDone() bool {
	// 检查所有跨度是否至少已开始清扫，并且
	// 没有活动清扫器。如果两者均为真，则所有跨距
	// 都已完成扫掠。
	return atomic.Load(&mheap_.sweepDrained) != 0 && atomic.Load(&mheap_.sweepers) == 0
}

// 仅当扫描范围s时返回。
// go:nowritebarrier 
func (s *mspan) ensureSwept() {
	// 调用方必须禁用抢占。
	// 否则，当此函数返回时，跨度可能再次变为未扫描
	// （如果在另一个goroutine上触发GC）。
	_g_ := getg()
	if _g_.m.locks == 0 && _g_.m.mallocing == 0 && _g_ != _g_.m.g0 {
		throw("mspan.ensureSwept: m is not locked")
	}

	sl := newSweepLocker()
	// 调用者必须确保span是mSpanInUse span。
	if s, ok := sl.tryAcquire(s); ok {
		s.sweep(false)
		sl.dispose()
		return
	}
	sl.dispose()

	// 不幸的情况，我们没有有效的方法等待
	for {
		spangen := atomic.Load(&s.sweepgen)
		if spangen == sl.sweepGen || spangen == sl.sweepGen+3 {
			break
		}
		osyield()
	}
}

// 扫描释放或收集未在标记阶段标记的块的终结器。
// 它清除标记位，为下一轮GC做准备。
// 如果跨度返回到堆，则返回true。
// 如果preserve=true，则不要将其返回堆，也不要在mcentral列表中重新链接；
// 呼叫方负责处理。ABCFDG＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊
func (sl *sweepLocked) sweep(preserve bool) bool {
	_g_ := getg()
	if _g_.m.locks == 0 && _g_.m.mallocing == 0 && _g_ != _g_.m.g0 {
		throw("mspan.sweep: m is not locked")
	}

	s := sl.mspan
	if !preserve {
		// 我们将释放此跨度的所有权。Nil out to 
		// 防止调用方意外使用它。
		sl.mspan = nil
	}

	sweepgen := mheap_.sweepgen
	if state := s.state.get(); state != mSpanInUse || s.sweepgen != sweepgen-1 {
		print("mspan.sweep: state=", state, " sweepgen=", s.sweepgen, " mheap.sweepgen=", sweepgen, "\n")
		throw("mspan.sweep: bad span state")
	}

	if trace.enabled {
		traceGCSweepSpan(s.npages * _PageSize)
	}

	atomic.Xadd64(&mheap_.pagesSwept, int64(s.npages))

	spc := s.spanclass
	size := s.elemsize

	// allocBits表示哪些未标记的对象不需要被处理，因为它们在上一个GC循环结束时是空闲的
	// 并且此后未被分配。
	// 如果allocBits索引>=s.freeindex，且未标记位
	// 则自上次GC以来，该对象保持未分配
	// 状态。
	// 这种情况类似于在自由名单上。

	// 为我们即将释放的任何对象取消链接并释放特殊记录。这里有两个并发症：
	// 1。对象可以同时具有终结器和配置文件特殊记录。
	// 在这种情况下，我们需要将终结器排队执行，
	// 将对象标记为活动并保留特殊配置文件。
	// 2。一个微小的对象可以为不同的偏移设置多个终结器。
	// 如果未标记此对象，则需要立即将所有终结器排队。
	// 1和2都可以同时使用。
	hadSpecials := s.specials != nil
	siter := newSpecialsIter(s)
	for siter.valid() {
		// 可以为对象的内部字节“查找对象开头”设置终结器。
		objIndex := uintptr(siter.s.offset) / size
		p := s.base() + objIndex*size
		mbits := s.markBitsForIndex(objIndex)
		if !mbits.isMarked() {
			// 此对象未标记且至少有一条特殊记录。
			// 传递1:查看它是否至少有一个终结器。
			hasFin := false
			endOffset := p - s.base() + size
			for tmp := siter.s; tmp != nil && uintptr(tmp.offset) < endOffset; tmp = tmp.next {
				if tmp.kind == _KindSpecialFinalizer {
					// 如果对象具有终结器，请停止释放该对象。
					mbits.setMarkedNonAtomic()
					hasFin = true
					break
				}
			}
			// 传递2：将所有终结器排队或处理配置文件记录。
			for siter.valid() && uintptr(siter.s.offset) < endOffset {
				// 查找为其设置特殊设置的确切字节
				// （与对象开头相反）。
				special := siter.s
				p := s.base() + uintptr(special.offset)
				if special.kind == _KindSpecialFinalizer || !hasFin {
					siter.unlinkAndNext()
					freeSpecial(special, unsafe.Pointer(p), size)
				} else {
					// 该对象具有终结器，因此我们将使其保持活动状态。
					// 所有其他特殊功能仅在对象被释放时适用，
					// 因此只需保留特殊记录即可。
					siter.next()
				}
			}
		} else {
			// 对象仍处于活动状态
			if siter.s.kind == _KindSpecialReachable {
				special := siter.unlinkAndNext()
				(*specialReachable)(unsafe.Pointer(special)).reachable = true
				freeSpecial(special, unsafe.Pointer(p), size)
			} else {
				// 保留特殊记录
				siter.next()
			}
		}
	}
	if hadSpecials && s.specials == nil {
		spanHasNoSpecials(s)
	}

	if debug.allocfreetrace != 0 || debug.clobberfree != 0 || raceenabled || msanenabled {
		// 查找所有新释放的对象。这不需要
		// 高效；allocfreetrace具有巨大的开销。
		mbits := s.markBitsForBase()
		abits := s.allocBitsForIndex(0)
		for i := uintptr(0); i < s.nelems; i++ {
			if !mbits.isMarked() && (abits.index < s.freeindex || abits.isMarked()) {
				x := s.base() + i*s.elemsize
				if debug.allocfreetrace != 0 {
					tracefree(unsafe.Pointer(x), size)
				}
				if debug.clobberfree != 0 {
					clobberfree(unsafe.Pointer(x), size)
				}
				if raceenabled {
					racefree(unsafe.Pointer(x), size)
				}
				if msanenabled {
					msanfree(unsafe.Pointer(x), size)
				}
			}
			mbits.advance()
			abits.advance()
		}
	}

	// 检查僵尸对象。
	if s.freeindex < s.nelems {
		// 所有<freeindex的内容都已分配，因此
		// 不能成为僵尸。
		// 
		// 检查第一个位图字节，其中必须是
		// 小心使用freeindex。
		obj := s.freeindex
		if (*s.gcmarkBits.bytep(obj / 8)&^*s.allocBits.bytep(obj / 8))>>(obj%8) != 0 {
			s.reportZombies()
		}
		// 检查剩余字节。
		for i := obj/8 + 1; i < divRoundUp(s.nelems, 8); i++ {
			if *s.gcmarkBits.bytep(i)&^*s.allocBits.bytep(i) != 0 {
				s.reportZombies()
			}
		}
	}

	// 计算此范围内的可用对象数。
	nalloc := uint16(s.countAlloc())
	nfreed := s.allocCount - nalloc
	if nalloc > s.allocCount {
		// 上面的僵尸检查应该在
		// 中捕捉到更多细节。
		print("runtime: nelems=", s.nelems, " nalloc=", nalloc, " previous allocCount=", s.allocCount, " nfreed=", nfreed, "\n")
		throw("sweep increased allocation count")
	}

	s.allocCount = nalloc
	s.freeindex = 0 // 将分配索引重置为span的开始。
	if trace.enabled {
		getg().m.p.ptr().traceReclaimed += uintptr(nfreed) * s.elemsize
	}

	// gcmarkBits成为allocBits。
	// 获取一个新的已清除gcmarkBits，为下一个GC做准备
	s.allocBits = s.gcmarkBits
	s.gcmarkBits = newMarkBits(s.nelems)

	// 初始化alloc位缓存。
	s.refillAllocCache(0)

	// 在我们更新sweepgen之前，span必须归我们独家所有，
	// 检查潜在的种族。
	if state := s.state.get(); state != mSpanInUse || s.sweepgen != sweepgen-1 {
		print("mspan.sweep: state=", state, " sweepgen=", s.sweepgen, " mheap.sweepgen=", sweepgen, "\n")
		throw("mspan.sweep: bad span state after sweep")
	}
	if s.sweepgen == sweepgen+1 || s.sweepgen == sweepgen+3 {
		throw("swept cached span")
	}

	// 我们只需要在扫描所有块时设置s.sweepgen=h.sweepgen，
	// 因为可能存在并发空闲/设置终结器。
	// 
	// 但我们需要先设置它，然后才能使范围可用于分配
	// （将其返回到heap或mcentral），因为分配代码假定如果可用于分配，
	// 范围已被扫描。
	// 
	// 序列化点。
	// 此时标记位被清除，分配准备就绪
	// 开始，因此释放量程。
	atomic.Store(&s.sweepgen, sweepgen)

	if spc.sizeclass() != 0 {
		// 小对象的句柄跨度。
		if nfreed > 0 {
			// 仅当我们释放了任何
			// 对象时，才将跨度标记为需要调零，因为分配到
			// 中的新跨度没有完全填满，但经过扫掠后，其所有
			// 空闲插槽仍为零。
			s.needzero = 1
			stats := memstats.heapStats.acquire()
			atomic.Xadduintptr(&stats.smallFreeCount[spc.sizeclass()], uintptr(nfreed))
			memstats.heapStats.release()
		}
		if !preserve {
			// 调用者可能没有从任何
			// 未设置的设置中删除此span，但通过更新sweepgen获取了
			// 扫描的span的所有权。如果此跨距仍在
			// 未扫描的集合中，则mcentral会将其从
			// 集合中弹出，检查其扫描根，并忽略它。
			if nalloc == 0 {
				// 完全释放的跨度直接返回堆。
				mheap_.freeSpan(s)
				return true
			}
			// 返回右边的mcentral列表。
			if uintptr(nalloc) == s.nelems {
				mheap_.central[spc].mcentral.fullSwept(sweepgen).push(s)
			} else {
				mheap_.central[spc].mcentral.partialSwept(sweepgen).push(s)
			}
		}
	} else if !preserve {
		// 大型对象的句柄范围。
		if nfreed != 0 {
			// 将大对象范围释放到堆。

			// 注意（rsc，dvyukov）：CL 22060046中efence 
			// 的原始实现使用了sysFree而不是sysFault，因此
			// 操作系统最终会再次将内存
			// 返回给我们，这样一个efence程序就可以在不耗尽内存的情况下运行
			// 更长的时间。不幸的是，在这里调用sysFree而不对堆数据结构进行任何调整意味着当内存返回时，它的元数据错误，无论是在mspan结构中还是在垃圾收集位图中。
			// 在这里使用sysFault意味着程序将在efence模式下相当快地耗尽
			// 内存，但至少不会
			// 由于内存重用混乱而导致神秘的崩溃。
			// 如果我们也实现并调用某种mheap.deleteSpan，那么应该可以切换回sysFree。
			if debug.efence > 0 {
				s.limit = 0 // 阻止mlookup查找此跨度
				sysFault(unsafe.Pointer(s.base()), size)
			} else {
				mheap_.freeSpan(s)
			}
			stats := memstats.heapStats.acquire()
			atomic.Xadduintptr(&stats.largeFreeCount, 1)
			atomic.Xadduintptr(&stats.largeFree, size)
			memstats.heapStats.release()
			return true
		}

		// 直接将大跨度添加到完整+扫描列表中。
		mheap_.central[spc].mcentral.fullSwept(sweepgen).push(s)
	}
	return false
}

// reportZombies报告s和抛出中任何已标记但可用的对象。
// 
// 这通常意味着以下情况之一：
// 
// 1。用户代码将指针转换为一个uintptr，然后不安全地返回
// 并且当uintptr是对
// 对象的唯一引用时运行GC。中国英语学习网编译：中国英语学习网编译：中国英语学习网编译：中国英语学习网编译：中国英语学习网编译：中国英语学习网编译：中国英语学习网编译：中国英语学习网编译：中国英语学习网编译。用户代码（或编译器错误）构造了一个错误指针，
// 指向一个空闲插槽，通常是一个超过结束指针的插槽。中国英语学习网编译：中国英语学习网编译：中国英语学习网编译：中国英语学习网编译：中国英语学习网编译。两个周期前的GC丢失了一个指针并释放了一个活动对象
// 但它在上一个周期中仍然处于活动状态，因此此GC周期找到了指向该对象的
// 指针并对其进行了标记。
func (s *mspan) reportZombies() {
	printlock()
	print("runtime: marked free object in span ", s, ", elemsize=", s.elemsize, " freeindex=", s.freeindex, " (bad use of unsafe.Pointer? try -d=checkptr)\n")
	mbits := s.markBitsForBase()
	abits := s.allocBitsForIndex(0)
	for i := uintptr(0); i < s.nelems; i++ {
		addr := s.base() + i*s.elemsize
		print(hex(addr))
		alloc := i < s.freeindex || abits.isMarked()
		if alloc {
			print(" alloc")
		} else {
			print(" free ")
		}
		if mbits.isMarked() {
			print(" marked  ")
		} else {
			print(" unmarked")
		}
		zombie := mbits.isMarked() && !alloc
		if zombie {
			print(" zombie")
		}
		print("\n")
		if zombie {
			length := s.elemsize
			if length > 1024 {
				length = 1024
			}
			hexdumpWords(addr, addr+length, nil)
		}
		mbits.advance()
		abits.advance()
	}
	throw("found pointer to free object")
}

// deductSweepCredit为分配范围
// size spanBytes而扣除扫描信用。必须在*分配范围
// 之前*执行此操作，以确保系统具有足够的信用。如有必要，它将进行清扫，以防止负债。如果调用者将
// 也扫描页面（例如，对于较大的分配），则它可以传递
// 非零调用者sweepPages，以保留这么多未扫描的页面。
// 
// deductSweepCredit做出最坏情况的假设，即最终分配的跨距的所有跨距字节
// 将可用于对象
// 分配。
// 
// 扣除扫款信用是“比例扫款”系统的核心。
// 它使用垃圾收集器收集的统计信息执行
// 足够的扫描，以便在GC循环之间的并发
// 扫描阶段扫描所有页面。不能锁定
// 
// mheap_u。
func deductSweepCredit(spanBytes uintptr, callerSweepPages uintptr) {
	if mheap_.sweepPagesPerByte == 0 {
		// 完成或禁用比例扫描。
		return
	}

	if trace.enabled {
		traceGCSweepStart()
	}

retry:
	sweptBasis := atomic.Load64(&mheap_.pagesSweptBasis)

	// 必要时修复债务。
	newHeapLive := uintptr(atomic.Load64(&gcController.heapLive)-mheap_.sweepHeapLiveBasis) + spanBytes
	pagesTarget := int64(mheap_.sweepPagesPerByte*float64(newHeapLive)) - int64(callerSweepPages)
	for pagesTarget > int64(atomic.Load64(&mheap_.pagesSwept)-sweptBasis) {
		if sweepone() == ^uintptr(0) {
			mheap_.sweepPagesPerByte = 0
			break
		}
		if atomic.Load64(&mheap_.pagesSweptBasis) != sweptBasis {
			// 扫描速度已更改。重新计算债务。
			goto retry
		}
	}

	if trace.enabled {
		traceGCSweepDone()
	}
}

// clobberfree将x处的内存内容设置为坏内容，用于调试
// 目的。
func clobberfree(x unsafe.Pointer, size uintptr) {
	// 大小（span.elemsize）始终是4的倍数。
	for i := uintptr(0); i < size; i += 4 {
		*(*uint32)(add(x, i)) = 0xdeadbeef
	}
}
